package bancosys.tec.services.implementor.annotations.visitor;

import java.lang.annotation.Annotation;
import java.lang.reflect.Member;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;

import jmine.tec.di.annotation.visitor.InjectedAnnotationVisitor;
import jmine.tec.di.cache.TypeMatchLookup;
import jmine.tec.di.metadata.DependencyMetaData;
import jmine.tec.di.metadata.impl.DependencyMetaDataImpl;
import jmine.tec.di.resolver.impl.SuffixOfObjectResolver;
import jmine.tec.di.type.NoTypeConverter;
import jmine.tec.di.type.TypeConverter;
import jmine.tec.di.wrapper.Wrapper;
import jmine.tec.di.wrapper.Wrappers;
import jmine.tec.services.annotations.SuffixOf;
import jmine.tec.utils.Tuple;
import jmine.tec.utils.UnmodifiableTuple;
import jmine.tec.utils.reflection.GenericTypeResolverImpl;

/**
 * AnnotationVisitor para {@link SuffixOf}
 * 
 * @author takeshi
 */
public class SuffixOfAnnotationVisitor extends SetterAndParametersAnnotationVisitor<DependencyMetaData> {

    private final TypeMatchLookup<TypeConverter<?>> typeConverters;

    /**
     * @param typeConverters o sistema de conversao de tipos
     */
    public SuffixOfAnnotationVisitor(TypeMatchLookup<TypeConverter<?>> typeConverters) {
        super(SuffixOf.class);
        this.typeConverters = typeConverters;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Collection<? extends DependencyMetaData> onParameterAnnotation(Annotation annotation, Class<?> annotated, int parameterIndex,
            Class<?> parameterType, Type genericType, Member annotatedMember) {
        return this.onPropertySetterAnnotation(annotation, annotated, ((SuffixOf) annotation).preffix() + "*", parameterType, genericType);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Collection<? extends DependencyMetaData> onPropertySetterAnnotation(Annotation annotation, Class<?> annotated,
            String propertyName, Class<?> propertyRawType, Type genericType) {
        Tuple<Class<?>, Class<?>> tuple = extractKeyAndValueTypes(annotated, genericType);
        TypeConverter<?> keyTypeConverter = this.typeConverters.lookup(tuple.head(), NoTypeConverter.getInstance());
        TypeConverter<?> valueTypeConverter = this.typeConverters.lookup(tuple.tail(), NoTypeConverter.getInstance());
        SuffixOf suffix = (SuffixOf) annotation;
        Wrapper wrapper = Wrappers.findWrapperFor(propertyRawType);
        SuffixOfObjectResolver resolver =
                new SuffixOfObjectResolver(tuple.tail(), valueTypeConverter, suffix.preffix(), tuple.head(), keyTypeConverter);
        return Collections
                .singleton(new DependencyMetaDataImpl(suffix.preffix() + "*", resolver, suffix.required(), wrapper, tuple.tail()));
    }

    /**
     * Extrai o tipo da chave e valor quando o generic type eh um {@link ParameterizedType} e tem exatamente 2 argumentos - como
     * {@link Tuple}. Caso contrario, devolve uma Tuple com String e o tipo do valor.
     * 
     * @param annotated a classe com a annotation
     * @param genericType a declaracao generica
     * @return {@link Tuple}
     */
    public static Tuple<Class<?>, Class<?>> extractKeyAndValueTypes(Class<?> annotated, Type genericType) {
        Class<?> keyType = String.class;
        Class<?> componentType;
        if (genericType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) genericType;
            final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            if (actualTypeArguments.length == 2) {
                keyType = GenericTypeResolverImpl.getInstance().resolveTypeForParameterizedType(parameterizedType, annotated, 0);
            }
            componentType =
                    GenericTypeResolverImpl.getInstance().resolveTypeForParameterizedType(parameterizedType, annotated,
                            actualTypeArguments.length - 1);
        } else {
            componentType = InjectedAnnotationVisitor.resolveGenericType(genericType, annotated);
        }
        Tuple<Class<?>, Class<?>> tuple = new UnmodifiableTuple<Class<?>, Class<?>>(keyType, componentType);
        return tuple;
    }

}
